<?php
// +----------------------------------------------------------------------
// | 云静Admin
// +----------------------------------------------------------------------
// | Copyright (c) 2019-2022 http://www.iyunj.cn
// +----------------------------------------------------------------------
// | 云静Admin提供个人非商业用途免费使用。
// +----------------------------------------------------------------------
// | Author: Uncle-L <1071446619@qq.com>
// +----------------------------------------------------------------------
// | 应用的公共函数库
// +----------------------------------------------------------------------

use think\facade\Db;
use yunj\core\Config;
use yunj\core\enum\Def;
use yunj\core\Validate;
use think\response\View;
use yunj\library\lock\Lock;
use think\response\Redirect;
use yunj\core\response\Json;
use think\Model as ThinkModel;
use yunj\core\enum\TipsTemplet;
use yunj\core\builder\YunjForm;
use yunj\core\builder\YunjTable;
use yunj\core\builder\YunjImport;
use think\facade\Log as ThinkLog;
use yunj\core\response\ErrorJson;
use yunj\core\response\SuccessJson;
use yunj\core\constants\BuilderConst;
use think\facade\Cache as ThinkCache;
use yunj\core\exception\GeneralException;
use yunj\core\builder\Builder as YunjBuilder;
use yunj\core\exception\ResponseJsonException;
use yunj\app\admin\service\setting\SettingService;
use yunj\core\YunjCaptcha;

if (!function_exists('is_initialized')) {
    /**
     * 是否已初始化
     * @return bool
     */
    function is_initialized(): bool {
        return file_exists(ds_replace(YUNJ_PATH . '/initialized.log'));
    }
}

if (!function_exists('yunj_config')) {
    /**
     * Notes: 基础配置
     * Author: Uncle-L
     * Date: 2020/10/20
     * Time: 14:45
     * @param string $key [配置参数名，第一级为配置文件名。支持多级配置 .号分割。如：yunj_config('errcode.10000')]
     * @param mixed $default [默认值]
     * @return mixed
     */
    function yunj_config(string $key, $default = null) {
        return Config::get($key, $default, YUNJ_PATH . "config/", YUNJ_VENDOR_SRC_PATH . "config/");
    }
}

if (!function_exists('yunj_setting')) {
    /**
     * 设置值获取
     * @param string $key 如：sys.upload_img_ext 获取系统设置的上传图片格式配置
     * @return mixed|null
     */
    function yunj_setting(string $key) {
        list($group, $key) = explode('.', $key);
        /** @var SettingService $settingService */
        $settingService = app(SettingService::class);
        return $settingService->setGroup($group)->value($key);
    }
}

if (!function_exists('YF')) {
    /**
     * Notes: 创建一个表单构建器实例
     * Author: Uncle-L
     * Date: 2021/10/9
     * Time: 11:59
     * @param string $id
     * @param array $args
     * @return YunjForm
     */
    function YF(string $id, array $args = []): YunjForm {
        return new YunjForm($id, $args);
    }
}

if (!function_exists('YT')) {
    /**
     * Notes: 创建一个表格构建器实例
     * Author: Uncle-L
     * Date: 2021/10/9
     * Time: 12:00
     * @param string $id
     * @param array $args
     * @return YunjTable
     */
    function YT(string $id, array $args = []): YunjTable {
        return new YunjTable($id, $args);
    }
}

if (!function_exists('YI')) {
    /**
     * Notes: 创建一个导入构建器实例
     * Author: Uncle-L
     * Date: 2021/10/9
     * Time: 12:00
     * @param string $id
     * @param array $args
     * @return YunjImport
     */
    function YI(string $id, array $args = []): YunjImport {
        return new YunjImport($id, $args);
    }
}

if (!function_exists('yunj_vender_src_view_template')) {
    /**
     * Notes: src视图模板地址
     * Author: Uncle-L
     * Date: 2021/11/3
     * Time: 16:02
     * @param string $template
     * @return string
     */
    function yunj_vender_src_view_template(string $template): string {
        $path = "../" . YUNJ_VENDOR_SHORT_PATH . "src/view/";
        $template .= substr($template, -5) !== ".html" ? ".html" : "";
        return $path . $template;
    }
}

if (!function_exists('yunj_vender_app_demo_view_template')) {
    /**
     * Notes: demo视图模板地址
     * Author: Uncle-L
     * Date: 2021/11/3
     * Time: 16:02
     * @param string $template
     * @return string
     */
    function yunj_vender_app_demo_view_template(string $template): string {
        $path = "../" . YUNJ_VENDOR_SHORT_PATH . "src/app/demo/view/";
        $template .= substr($template, -5) !== ".html" ? ".html" : "";
        return $path . $template;
    }
}

if (!function_exists('yunj_vender_app_admin_view_template')) {
    /**
     * Notes: admin视图模板地址
     * Author: Uncle-L
     * Date: 2021/11/3
     * Time: 16:02
     * @param string $template
     * @return string
     */
    function yunj_vender_app_admin_view_template(string $template): string {
        $path = "../" . YUNJ_VENDOR_SHORT_PATH . "src/app/admin/view/";
        $template .= substr($template, -5) !== ".html" ? ".html" : "";
        return $path . $template;
    }
}

if (!function_exists('view_template')) {
    /**
     * Notes: 视图模板地址
     * Author: Uncle-L
     * Date: 2021/11/3
     * Time: 16:02
     * @param string $template
     * @return string
     */
    function view_template(string $template): string {
        $path = "../" . YUNJ_VENDOR_SHORT_PATH . "src/view/";
        $template .= substr($template, -5) !== ".html" ? ".html" : "";
        return $path . $template;
    }
}

if (!function_exists('view_builder')) {
    /**
     * 渲染表格模板输出
     * @param YunjBuilder|null $builder 构建器实例
     * @param string $template 模板文件
     * @param array $vars 模板变量
     * @param int $code 状态码
     * @param callable $filter 内容过滤
     * @return View|mixed
     * @throws GeneralException
     */
    function view_builder(?YunjBuilder $builder, string $template, array $vars = [], int $code = 200, callable $filter = null) {
        if ($builder) {
            if (request()->isAjax()) return $builder->async();
            $builderType = $builder->getType();
            $builderViewArgs = $builder->viewArgs();
            $builderScriptFileList = $builder->getScriptFileList();
            $builderId = $builder->getId();
            $vars = [
                    BuilderConst::ID_KEY => $builderId,
                    $builderType => [$builderId => $builderViewArgs],
                    "scriptFileList" => $builderScriptFileList
                ] + $vars;
        }
        $template = view_template($template);
        return view($template, $vars, $code, $filter);
    }
}

if (!function_exists('view_form')) {
    /**
     * 渲染表单模板输出
     * @param YunjForm|null $builder 表单构建器实例
     * @param array $vars 模板变量
     * @param int $code 状态码
     * @param callable $filter 内容过滤
     * @return View|mixed
     * @throws GeneralException
     */
    function view_form(?YunjForm $builder = null, array $vars = [], int $code = 200, callable $filter = null) {
        return view_builder($builder, "admin/template/form", $vars, $code, $filter);
    }
}

if (!function_exists('view_table')) {
    /**
     * 渲染表格模板输出
     * @param YunjTable|null $builder 表格构建器实例
     * @param array $vars 模板变量
     * @param int $code 状态码
     * @param callable $filter 内容过滤
     * @return View|mixed
     * @throws GeneralException
     */
    function view_table(?YunjTable $builder = null, array $vars = [], int $code = 200, callable $filter = null) {
        return view_builder($builder, "admin/template/table", $vars, $code, $filter);
    }
}

if (!function_exists('view_import')) {
    /**
     * 渲染导入模板输出
     * @param YunjImport|null $builder 导入构建器实例
     * @param array $vars 模板变量
     * @param int $code 状态码
     * @param callable $filter 内容过滤
     * @return View|mixed
     * @throws GeneralException
     */
    function view_import(?YunjImport $builder = null, array $vars = [], int $code = 200, callable $filter = null) {
        return view_builder($builder, "admin/template/import", $vars, $code, $filter);
    }
}

if (!function_exists('request_not_confirmed')) {
    /**
     * 请求是否未确认（适用于有确认反馈弹窗的异步请求）
     * @return bool
     */
    function request_not_confirmed(): bool {
        return !request()->header(Def::CONFIRMED_HEADER_KEY, false);
    }
}

if (!function_exists('default_image_url')) {
    /**
     * 返回默认图片地址
     * @return string
     */
    function default_image_url(): string {
        $imgUrl = yunj_setting('sys.default_img');
        $imgUrl = $imgUrl ?: Def::IMG_URI;
        return substr($imgUrl, 0, 4) == 'http' ? $imgUrl : (request_domain() . $imgUrl);
    }
}

/****************************************************** 跳转 ******************************************************/

if (!function_exists('build_url')) {
    /**
     * 构建URL地址
     * @param string $url 路由地址
     * @param array $vars 变量
     * @param bool|string $suffix 生成的URL后缀
     * @param bool|string $domain 域名
     * @return string
     */
    function build_url(string $url = '', array $vars = [], $suffix = true, $domain = false) {
        return url($url, $vars, $suffix, $domain)->build();
    }
}

if (!function_exists("class_route")) {
    /**
     * 类方法的路由地址
     * @param string|array|callable $class
     * @param string $action
     * @return string
     */
    function class_route($class, string $action = '') {
        if (is_array($class)) list($class, $action) = $class;
        return $class . '@' . $action;
    }
}

if (!function_exists("class_url")) {
    /**
     * 类方法的url地址
     * @param string $class 类名
     * @param string $action 操作方法名
     * @param array $vars 变量
     * @param bool|string $suffix 生成的URL后缀
     * @param bool|string $domain 域名
     * @return string
     */
    function class_url(string $class, string $action, array $vars = [], $suffix = true, $domain = false) {
        return build_url(class_route($class, $action), $vars, $suffix, $domain);
    }
}

if (!function_exists('tips_url')) {
    /**
     * Notes: 提示页面地址
     * Author: Uncle-L
     * Date: 2020/10/21
     * Time: 17:54
     * @param TipsTemplet|null|mixed $templet [提示模板]
     * @param string $msg [提示消息]
     * @return string
     */
    function tips_url($templet = null, string $msg = ''): string {
        if (!($templet instanceof TipsTemplet)) {
            if (TipsTemplet::isValue($templet)) {
                $templet = TipsTemplet::byValue($templet);
            } else {
                $templet = TipsTemplet::ERROR();
            }
        }
        $vars = input('get.');
        // 由于 TP6 采用了类似于 Laravel 的路由系统，并使用 s 参数来指定控制器方法的调用。所以此处要去掉s
        if (array_key_exists('s', $vars) && $vars['s'] && substr($vars['s'], 0, 1) == '/') {
            unset($vars['s']);
        }
        $vars['templet'] = $templet->getValue();
        if ($msg && is_string($msg)) $vars['msg'] = $msg;
        return build_url('/' . YUNJ_ADMIN_ENTRANCE . '/tips') . "?" . http_build_query($vars);
    }
}

if (!function_exists('redirect_tips')) {
    /**
     * Notes: 重定向到提示页
     * Author: Uncle-L
     * Date: 2020/10/21
     * Time: 18:09
     * @param TipsTemplet|null|mixed $templet [提示模板]
     * @param string $msg [提示消息]
     * @return Redirect
     */
    function redirect_tips($templet = null, string $msg = ''): Redirect {
        return redirect(tips_url($templet, $msg));
    }
}

if (!function_exists('throw_redirect')) {
    /**
     * Notes: 抛出重定向
     * Author: Uncle-L
     * Date: 2021/10/19
     * Time: 14:53
     * @param string $url
     */
    function throw_redirect(string $url): void {
        // 重定向到当前地址不行
        if (strstr(request()->url(), $url)) return;
        header('content-type:text/html;charset=uft-8');
        header('location:' . $url);
        exit;
    }
}

if (!function_exists('handle_route_rule')) {
    /**
     * 处理路由地址规则
     * @param string $rule
     * @return string
     */
    function handle_route_rule(string $rule): string {
        // 替换连续的"////"为一个"/"，并移除开头结尾的"/"，只有"/"除外
        $rule = preg_replace('/\/+/', '/', $rule);
        return strlen($rule) > 1 ? trim($rule, '/') : $rule;
    }
}

if (!function_exists('handle_route_namespace')) {
    /**
     * 处理路由命名空间
     * @param string $namespace
     * @return string
     */
    function handle_route_namespace(string $namespace): string {
        // 替换连续的"\\\"为一个"\"
        return preg_replace('/\\\+/', '\\', $namespace);
    }
}

if (!function_exists('handle_base_url')) {
    /**
     * 处理基础url地址
     * @param string $url
     * @return string
     */
    function handle_base_url(string $url): string {
        // 替换连续的"////"为一个"/"
        $url = preg_replace('/\/+/', '/', $url);
        // 开头补充 '/'
        if (substr($url, 0, 1) != '/') {
            $url = '/' . $url;
        }
        // 去掉结尾的 '/'
        if (substr($url, -1) == '/') {
            $url = substr($url, 0, -1);
        }
        // 去掉.html等后缀
        if (strstr($url, '.')) {
            $url = substr($url, 0, strpos($url, '.', 0));
        }
        return $url;
    }
}

if (!function_exists('get_controller_action_page_url')) {
    /**
     * 获取控制器方法的页面地址
     * @param string $controller
     * @param string $action
     * @return string
     */
    function get_controller_action_page_url(string $controller, string $action): string {
        $controller = handle_route_namespace('\\' . $controller);
        /** @var \yunj\app\admin\enum\RedisKey $redisKey */
        $redisKey = \yunj\app\admin\enum\RedisKey::ADMIN_PAGE_BASE_URL_BY_CONTROLLER_ACTION();
        $url = $redisKey->setArgs($controller, $action)->getCacheValue(86400, true);
        return $url;
    }
}

/****************************************************** 输出 ******************************************************/

if (!function_exists('error_msg')) {
    /**
     * Notes: 返回错误码对应消息
     * Author: Uncle-L
     * Date: 2020/7/20
     * Time: 13:56
     * @param int $errcode
     * @param string $msg
     * @return string
     */
    function error_msg(int $errcode = 10000, string $msg = '') {
        return $errcode < 90000 ? Config::get('errcode.' . $errcode, $msg) : yunj_config('errcode.' . $errcode, $msg);
    }
}

if (!function_exists('response_json')) {
    /**
     * Notes: 返回固定格式json数据
     * Author: Uncle-L
     * Date: 2020/7/8
     * Time: 22:19
     * @param int $errcode
     * @param string $msg
     * @param mixed $data
     * @return Json
     */
    function response_json(int $errcode = 10000, string $msg = '', $data = null) {
        $msg = $msg ?: error_msg($errcode) ?: '';
        $result = ['errcode' => $errcode, 'msg' => $msg, 'data' => $data];
        return $errcode ? ErrorJson::generate($result) : SuccessJson::generate($result);
    }
}

if (!function_exists('success_json')) {
    /**
     * Notes: 返回成功格式的json数据
     * Author: Uncle-L
     * Date: 2020/1/14
     * Time: 17:47
     * @param mixed $data
     * @param string $msg
     * @return SuccessJson
     */
    function success_json($data = null, string $msg = '') {
        $msg = $msg ?: error_msg(0);
        return response_json(0, $msg, $data);
    }
}

if (!function_exists('error_json')) {
    /**
     * Notes: 返回失败格式的json数据
     * Author: Uncle-L
     * Date: 2020/1/14
     * Time: 17:49
     * @param string $msg
     * @return ErrorJson
     */
    function error_json(string $msg = '', int $errcode = 10000) {
        $msg = $msg ?: error_msg($errcode);
        return response_json($errcode, $msg);
    }
}

if (!function_exists('confirm_json')) {
    /**
     * Notes: 返回失败格式的json数据
     * Author: Uncle-L
     * Date: 2020/1/14
     * Time: 17:49
     * @param string $msg
     * @return ErrorJson
     */
    function confirm_json(string $msg = '', $data = null) {
        $msg = $msg ?: error_msg(Def::CONFIRM_ERRCODE);
        return response_json(Def::CONFIRM_ERRCODE, $msg, $data);
    }
}

if (!function_exists('throw_json')) {
    /**
     * Notes: 抛出固定格式json数据
     * Author: Uncle-L
     * Date: 2020/7/8
     * Time: 22:24
     * @param int|Json $errcode
     * @param string $msg
     * @param mixed $data
     */
    function throw_json($errcode = 10000, string $msg = '', $data = null) {
        $response = $errcode instanceof Json ? $errcode : Json::generate(['errcode' => $errcode, 'msg' => $msg ?: error_json($errcode), 'data' => $data]);
        throw new ResponseJsonException($response);
    }
}

if (!function_exists('throw_success_json')) {
    /**
     * Notes: 抛出成功格式的json数据
     * Author: Uncle-L
     * Date: 2020/3/21
     * Time: 18:20
     * @param mixed $data
     * @param string $msg
     */
    function throw_success_json($data = null, string $msg = '') {
        $response = success_json($data, $msg);
        throw_json($response);
    }
}

if (!function_exists('throw_error_json')) {
    /**
     * Notes: 抛出错误格式的json数据
     * Author: Uncle-L
     * Date: 2020/3/21
     * Time: 18:21
     * @param string $msg
     */
    function throw_error_json(string $msg = '') {
        $response = error_json($msg);
        throw_json($response);
    }
}

if (!function_exists('throw_confirm_json')) {
    /**
     * Notes: 抛出确认格式的json数据
     * Author: Uncle-L
     * Date: 2020/3/21
     * Time: 18:21
     * @param string $msg
     * @param mixed $data
     */
    function throw_confirm_json(string $msg = '', $data = null) {
        $response = confirm_json($msg, $data);
        throw_json($response);
    }
}

if (!function_exists('throw_empty_json')) {
    /**
     * Notes: 抛出成功格式的空json数据
     * Author: Uncle-L
     * Date: 2020/3/21
     * Time: 18:20
     */
    function throw_empty_json() {
        if (request()->param(BuilderConst::ASYNC_TYPE_KEY) === 'items') {
            throw_success_json(['items' => []]);
        }
        if (request()->param(BuilderConst::ASYNC_TYPE_KEY) === 'count') {
            throw_success_json(['count' => 0]);
        }
        throw_success_json();
    }
}

/****************************************************** 验证 ******************************************************/

if (!function_exists('data_validate')) {
    /**
     * Notes: 数据验证
     * Author: Uncle-L
     * Date: 2021/10/21
     * Time: 14:22
     * @param Validate $validate [验证器]
     * @param array $rule [字段规则]
     * @param array $field [字段描述]
     * @param array $data [待验证数据]
     * @param string $scene [验证环境]
     * @param bool $batch [是否批量验证]
     * @return bool
     */
    function data_validate(Validate &$validate, array $rule, array $field, array &$data, string $scene, $batch = false): bool {
        // 设置参数
        $validate->setAttrRule($rule);
        $validate->setAttrField($field);
        $attrScene = $validate->getAttrScene();
        $attrScene[$scene] = array_keys($rule);
        $validate->setAttrScene($attrScene);
        // 开始校验
        $res = $validate->scene($scene)->batch($batch)->check($data);
        if ($res) {
            // 验证通过赋值处理后的数据给待处理数据
            $data = $validate->getData();
        }
        // 返回验证结果
        return $res;
    }
}

if (!function_exists('form_data_validate')) {
    /**
     * Notes: 表单数据验证
     * Author: Uncle-L
     * Date: 2021/10/20
     * Time: 18:09
     * @param Validate $validate [验证器]
     * @param array $fields [字段配置]
     * @param array $data [待验证数据]
     * @param string $scene [验证场景]  不能为空
     * @return bool
     */
    function form_data_validate(Validate &$validate, array $fields, array &$data, string $scene) {
        // 规则
        $attrRule = [];
        // 验证数据描述
        $attrField = [];
        foreach ($fields as $k => $field) {
            if (!isset($field['verify']) || !$field['verify']) continue;
            $attrRule[$k] = $field['verify'];
            $attrField[$k] = isset($field["verifyTitle"]) && $field['verifyTitle'] ? $field['verifyTitle'] : ($field['title'] ?? $k);
        }
        if (!$attrRule) return true;
        $res = data_validate($validate, $attrRule, $attrField, $data, $scene);
        return $res;
    }
}

/****************************************************** 日志 ******************************************************/

if (!function_exists('log_fix_message')) {
    /**
     * 日志固定消息
     * @return string
     */
    function log_fix_message(): string {
        if (app()->runningInConsole()) {
            $message = "";
            if (app()->has('commandInput')) {
                /** @var \think\console\Input $input */
                $input = app()->make('commandInput');
                $args = $input->getArguments();
                foreach ($args as $v) {
                    if ($v !== null && $v !== false) {
                        $message .= " " . $v;
                    }
                }
                $options = $input->getOptions();
                foreach ($options as $k => $v) {
                    if ($v !== null && $v !== false) {
                        $message .= " --{$k}={$v}";
                    }
                }
            }
            $message = "cli:" . ($message ? ("php think " . trim($message)) : "true");
        } else {
            $requestParam = array_value_short(request()->all());
            $message = "requestUrl:" . request()->url(true)
                . "\r\nrequestParam:" . json_encode($requestParam, JSON_UNESCAPED_UNICODE)
                . "\r\nrequestIp:" . request_ip();
        }
        return $message;
    }
}

if (!function_exists('log_info')) {
    /**
     * 日志记录信息
     * @param string $message
     * @param string $channel
     * @param ...$context
     */
    function log_info(string $message, string $channel = '', ...$context): void {
        if (!strstr($message, "Stack trace") && request()->param('trace_record')) {
            $message .= "\r\nStack trace:\r\n" . get_curr_trace_as_string() . "\r\n";
        }
        if ($context) {
            $message .= "\r\ncontext:\r\n" . json_encode($context, JSON_UNESCAPED_UNICODE) . "\r\n";
        }
        $message .= "\r\n" . log_fix_message();
        $channel ? ThinkLog::channel($channel)->info($message) : ThinkLog::info($message);
    }
}

if (!function_exists('log_error')) {
    /**
     * 日志记录错误
     * @param string $message
     * @param string $channel
     * @param ...$context
     */
    function log_error(string $message, string $channel = '', ...$context): void {
        if (!strstr($message, "Stack trace") && request()->param('trace_record')) {
            $message .= "\r\nStack trace:\r\n" . get_curr_trace_as_string() . "\r\n";
        }
        if ($context) {
            $message .= "\r\ncontext:\r\n" . json_encode($context, JSON_UNESCAPED_UNICODE) . "\r\n";
        }
        $message .= "\r\n" . log_fix_message();
        $channel ? ThinkLog::channel($channel)->error($message) : ThinkLog::error($message);
    }
}

if (!function_exists('log_exception')) {
    /**
     * 日志记录错误
     * @param Throwable $exception
     * @param string $channel
     * @param ...$context
     */
    function log_exception(Throwable $exception, string $channel = '', ...$context): void {
        log_error(exception_to_str($exception), $channel, ...$context);
    }
}

if (!function_exists('exception_to_str')) {
    /**
     * Notes: Exception异常对象转换为字符串显示
     * Author: Uncle-L
     * Date: 2021/10/18
     * Time: 16:11
     * @param Throwable $e
     * @return string
     */
    function exception_to_str(\Throwable $e): string {
        $str = get_class($e) . " " . $e->getMessage() . " in " . $e->getFile() . ":" . $e->getLine();
        $traceStr = "";
        $traceArr = explode('#', $e->getTraceAsString());
        foreach ($traceArr as $trace) {
            if (
                $trace
                && (!strstr($traceStr, "vendor") || strstr($traceStr, "vendor\\yunj"))
                && !strstr($traceStr, "yunj") && !strstr($trace, "think") && !strstr($trace, "{main}")
            ) {
                $traceStr .= "#" . $trace;
            }
        }
        $str .= "\r\nStack trace:\r\n" . rtrim($traceStr, "\r\n");
        return $str;
    }
}

if (!function_exists('get_curr_trace_as_string')) {
    /**
     * 获取当前调用的堆栈跟踪信息
     * @return string
     */
    function get_curr_trace_as_string(): string {
        $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
        // 移除当前函数（get_curr_trace_as_string）的调用记录
        array_shift($backtrace);
        $output = '';
        foreach ($backtrace as $index => $frame) {
            $traceStr = "#{$index} ";
            if (isset($frame['file'])) {
                $traceStr .= "{$frame['file']}({$frame['line']}): ";
            }
            if (isset($frame['class'])) {
                $traceStr .= $frame['class'];
                if (isset($frame['type'])) {
                    $traceStr .= $frame['type'];
                }
            }
            $traceStr .= $frame['function'] . "()\r\n";
            if (
                (!strstr($traceStr, "vendor") || strstr($traceStr, "vendor\\yunj"))
                && !strstr($traceStr, "{main}")
                && !strstr($traceStr, "log_info") && !strstr($traceStr, "log_error")
            ) {
                $output .= $traceStr;
            }
        }
        return $output;
    }
}

/****************************************************** 其他 ******************************************************/

if (!function_exists("redis")) {
    /**
     * 获取radis实例
     * @return Redis
     */
    function redis(): Redis {
        /** @var Redis $redis */
        $redis = ThinkCache::store('redis')->handler();
        return $redis;
    }
}

if (!function_exists("redis_full_key")) {
    /**
     * redis完整的key
     * @param $key
     * @return string
     */
    function redis_full_key($key): string {
        return config('cache.stores.redis.prefix') . $key;
    }
}

if (!function_exists("redis_keys")) {
    /**
     * 获取匹配的所有redis key.调用示例 redis_keys('*twst*') redis_keys('twst*')...
     * @param string $pattern
     * @return array
     */
    function redis_keys(string $pattern): array {
        $redis = redis();
        // 默认SCAN_NORETRY情况下有可能会返回空数组，设置成SCAN_RETRY，如果是空数组的话，将不返回继续扫描下去
        $redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY);
        $keys = [];
        while (true) {
            // 每次扫描1000条
            $_keys = $redis->scan($iterator, $pattern, 1000);
            $keys = array_merge($keys, $_keys ?? []);
            // 迭代结束,未找到匹配pattern的key
            if ($iterator === 0 || $iterator === '0') break;
            // 游标为null了,充值为0,继续扫描
            if ($iterator === null) $iterator = 0;
        }
        if ($keys) {
            // 去重
            $keys = array_flip($keys);
            $keys = array_flip($keys);
        }
        return $keys;
    }
}

if (!function_exists("password_handle")) {
    /**
     * 密码处理（根据传入密码和盐值生成密码md5的hash值）
     * @param string $password
     * @param string $salt
     * @return array
     */
    function password_handle(string $password, string $salt = ''): array {
        $salt = $salt ? $salt : rand_char(10);
        $hash = hash_hmac('md5', $password, $salt);
        return [$hash, $salt];
    }
}

if (!function_exists("icon_class")) {
    /**
     * 图标class
     * @param string $class
     * @return string
     */
    function icon_class(string $class): string {
        if (!$class) {
            return $class;
        }
        if (strstr($class, 'layui-icon-') && !strstr($class, 'layui-icon ')) {
            $class = 'layui-icon ' . $class;
        } elseif (strstr($class, 'yunj-icon-') && !strstr($class, 'yunj-icon ')) {
            $class = 'yunj-icon ' . $class;
        }
        return $class;
    }
}

if (!function_exists("db_table_exist")) {
    /**
     * 数据库表是否存在
     * @param string|ThinkModel $table
     * @return bool
     */
    function db_table_exist($table): bool {
        if ($table instanceof ThinkModel) $table = $table->getTable();
        if (!$table || !is_string($table)) return false;
        return !!Db::query("show tables like '{$table}'");
    }
}

if (!function_exists('snowflake_next_id')) {
    /**
     * 雪花算法生成id
     * @return string
     */
    function snowflake_next_id(): string {
        $snowflake = new \yunj\library\snowflake\Snowflake();
        $snowflake->setRedisConnCall(function () {
            return redis();
        });
        return $snowflake->nextId();
    }
}

if (!function_exists('lock')) {
    /**
     * 锁
     * @param string $key
     * @param int $ttl 锁过期时间
     * @param int $waitTime 等待时间（要求大于过期时间）
     * @return Lock
     * @throws ErrorException
     */
    function lock(string $key, int $ttl = 10, int $waitTime = 60): Lock {
        $lock = new Lock($key, $ttl, $waitTime);
        $lock->setRedisConnCall(function () {
            return redis();
        });
        return $lock->get();
    }
}

if (!function_exists('yunj_captcha')) {
    /**
     * 图片验证码
     * @return YunjCaptcha
     */
    function yunj_captcha(): YunjCaptcha {
        $captcha = new YunjCaptcha();
        $captcha->setRedisConnCall(function () {
            return redis();
        });
        return $captcha;
    }
}

if (!function_exists('sql_dump')) {
    /**
     * 监听后面执行的sql语句打印
     */
    function sql_dump() {


        Db::listen(function ($sql, $time, $master) {
            dump($sql);
            dump($time);
            dump($master);
        });

//        \Illuminate\Support\Facades\DB::beginTransaction();
//        \Illuminate\Support\Facades\DB::listen(function ($query) use ($block_after_first) {
//            $bindings = $query->bindings;
//            $sql = $query->sql;
//            foreach ($bindings as $replace) {
//                $value = is_numeric($replace) ? $replace : "'" . $replace . "'";
//                $sql = preg_replace('/\?/', $value, $sql, 1);
//
//            }
//            $sql .= ' 耗时:' . $query->time . 'mms';
//            $block_after_first == true ? dd($sql) : dump($sql);
////            $block_after_first == true ? dd($sql) : \Illuminate\Support\Facades\Log::info($sql);
//        });
    }
}